
/* softload.c - soft load a RISC OS ROM */

/*
   MJS 08-Sep-96 Derived from SoftLoad9 BASIC code, but:
                 - updated for RISC OS 3.7 and StrongARM
   MJS 23-Sep-97 Rewritten to be more flexible, and to be simpler by assuming
                 most of memory in Free Pool rather than AppSpace (via a
                 proper AppSize command in boot sequence)
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "kernel.h"
#include "swis.h"

#define Version "1.11"

#define DefaultROMPath  "<Boot$Dir>.SoftLoad."

/* ------------------------------------------------------------------------ */

#define AppSpaceStart    0x8000
#define ROMsize        0x400000     /* we deal only with 4M ROMs */
#define ROMlogaddr    0x3800000
#define ROMcheckaddr  0x03ffff4     /* offset in ROM for checksum word */
#define ROMhardaddr         0x0

/* RISC OS's flat copy of physical space, only for non-RAM on some versions */
#define PhysSpace    0x80000000

#define DA_FreePool     6           /* dynamic area number */

#define PageSize        0x1000
#define Log2PageSize    12

/* min machine memory to attempt softload */
#define AbsMinMemSize   (ROMsize + 0x200000)

#define ErrorReturnCode 257

/* ------------------------------------------------------------------------ */

typedef unsigned int uint32;

typedef struct
{
  uint32 logaddr;
  uint32 flags;
} CAMentry_t;

typedef struct
{
  uint32 addr;
  uint32 size;
} physmemtable_t;

typedef struct
{
  int    page;      /* first page number */
  uint32 physaddr;  /* start physical address */
  int    DAN;       /* dynamic area number */
  uint32 DAbase;    /* start logical address */
} softROM_t;

typedef struct
{
  uint32 PhysRamTable;
  uint32 CAMbase;
  uint32 PageFlags_Unavailable;
  uint32 ARMA_Cleaner_flipflop;
  uint32 L1PT;
} kernelvals_t;

typedef struct
{
  int    pagenum;
  uint32 logaddr;
  uint32 physaddr;
} osmemory0_t;

/* ------------------------------------------------------------------------ */

extern void   startnew(uint32, uint32, uint32, uint32);
extern uint32 svcpeek(uint32);
extern uint32 svcarmid(void);
extern void   DAhandler(void);

/* ------------------------------------------------------------------------ */

static int          verbose, min_memsize, Npages, ROMversion;
static softROM_t    softROM;
static kernelvals_t kernelvals;

/* ------------------------------------------------------------------------ */

static void error_noslot(char *code)
{
  fprintf(stderr,"-- softload failed; %s\n",code);
  exit(ErrorReturnCode);
}

/* ------------------------------------------------------------------------ */

static void find_kernelvals(int ROMversion, kernelvals_t *k)
{
  static int inblock[6] = { 0,2,3,4,13,-1 };

  static kernelvals_t k_360 = { 0x170, 0x1e02000, 0x2000, 0,     0x2c0c000 };
  static kernelvals_t k_370 = { 0x180, 0x1e02000, 0x2000, 0x178, 0x2c0c000 };

  uint32           outblock[5];
  _kernel_oserror *e;

  *k = k_360;
  if (ROMversion >= 370) *k = k_370;
  if (ROMversion >= 380)
  {
    e = _swix(OS_ReadSysInfo,_INR(0,2),6,inblock,outblock);
    if (e) error_noslot("failed to read kernel variables");
    k->CAMbase = *((uint32 *)outblock[0]);
    k->PageFlags_Unavailable = outblock[1];
    k->PhysRamTable = outblock[2];
    k->ARMA_Cleaner_flipflop = outblock[3];
    k->L1PT = outblock[4];
  }

}

/* ------------------------------------------------------------------------ */

static int read_arg_options(int argc, char *argv[],
                            int *verbose, int *min_memsize)
{
  /* set any options, return index of first filename arg, don't bother
     much with syntax checking */

  int  a;


  /* defaults */
  *verbose     = 0;
  *min_memsize = 0;

  for (a=1; (a<argc) && (argv[a][0]=='-'); a++)
  {
    /* -v[erbose] means verbose */
    if (strncmp(argv[a],"-verbose",strlen(argv[a])) == 0) *verbose = 1;

    /* -m[inmemory] <size>[K|M] sets min mem size to attempt softload,
       and disable error if minimum not reached */
    if (strncmp(argv[a],"-minmemory",strlen(argv[a])) == 0)
    {
      if (a < argc-1)
      {
        a++;
        *min_memsize = atoi(argv[a]);
        if ((strchr(argv[a],'k') != NULL) || (strchr(argv[a],'K') != NULL))
          *min_memsize = *min_memsize << 10;
        if ((strchr(argv[a],'m') != NULL) || (strchr(argv[a],'M') != NULL))
          *min_memsize = *min_memsize << 20;
        if (*min_memsize < AbsMinMemSize) *min_memsize = AbsMinMemSize;
      }
    }
  }

  return a;
}
/* ------------------------------------------------------------------------ */

static void read_ROMfname(int arg, char *argv[], char ROMfname[])
{
  /* prefix default pathname if arg string appears to be a simple leaf name */

  ROMfname[0] = '\0';
  if ((strchr(argv[arg],'.') == NULL) &&
      (strchr(argv[arg],':') == NULL) &&
      (strchr(argv[arg],'<') == NULL)    ) strcpy(ROMfname,DefaultROMPath);
  strcat(ROMfname,argv[arg]);
}

/* ------------------------------------------------------------------------ */

static int arewefine(char loadtype, uint32 currentROMPhysAddr,
                     char newROMfname[])
{
  /* see if current ROM state is what we want, checking ROM checksum words
     and current ROM physical address as necessary
      - loadtype must be one of 'u','f','r'
      - return value: 0=not fine, 1=fine, -1=ROM file not found or bad */

  int     i, fine;
  uint32  ow, nw;
  FILE    *fp;

  /* current ROM checksum */
  ow = *((uint32 *)(ROMlogaddr+ROMcheckaddr));

  switch(loadtype)
  {
    case 'u': /* fine if current ROM is hard ROM */
      fine = (currentROMPhysAddr == ROMhardaddr);
      break;
    case 'f': /* fine if soft loaded and checksum matches file */
      fp = fopen(newROMfname,"rb");
      if (fp == NULL)
        fine = -1;
      else
      {
        fseek(fp,(long int)ROMcheckaddr,SEEK_SET);
        i = fread(&nw,4,1,fp);
        fclose(fp);
        fine = ((currentROMPhysAddr != ROMhardaddr) && (ow == nw));
        if (i != 1) fine = -1;
      }
      break;
    case 'r': /* fine if soft loaded, and checksum matches hard ROM */
      nw = svcpeek(PhysSpace+ROMhardaddr+ROMcheckaddr);
      fine = ((currentROMPhysAddr != ROMhardaddr) && (ow == nw));
      break;
  }

  return fine;
}

/* ------------------------------------------------------------------------ */

static void find_ROMversion(void)
{
  _kernel_oserror *e;
  float           v;

  e = _swix(OS_Byte,_INR(0,1),0,0);
  sscanf(e->errmess,"RISC OS %f",&v);
  ROMversion = (int)(v*100.0F + 0.5F);
}

/* ------------------------------------------------------------------------ */

static uint32 ROM_physaddr(void)
{
  /* get physical start address of current ROM */

  uint32      mmu1, mmu2, romphysaddr;
  osmemory0_t mblock;

  mmu1 = svcpeek(kernelvals.L1PT + (ROMlogaddr >> 18));
  if ((mmu1 & 3) == 2)
  {
    /* section mapped */
    romphysaddr = mmu1 & 0xfff00000;
  }
  else
  {
    /* presumably page mapped due to ROM patching */
    mblock.physaddr = mmu1 & 0xfffffc00;
    _swix(OS_Memory,_INR(0,2),0x1400,&mblock,1); /* phys -> log transform */
    mmu2 = *((uint32 *)mblock.logaddr);
    romphysaddr = mmu2 & 0xfff00000;
  }

  return romphysaddr;
}

/* ------------------------------------------------------------------------ */

static void find_contiguous_chunk(int Npages, int pages_needed,
                                  int *chunk_page, uint32 *chunk_physaddr)
{
  int            chunk, max_chunk, pagenum, pages, p, foundpage;
  uint32         foundphysaddr;
  physmemtable_t *physmem;
  CAMentry_t     *CAM;

  /* kernel's table of physical RAM chunks */
  physmem = (physmemtable_t *)kernelvals.PhysRamTable;

  /* kernel's soft CAM */
  CAM = (CAMentry_t *)kernelvals.CAMbase;

  /* find end of physical memory chunk list */
  if (verbose)
    printf("physmem @ %8.8x\nchunk addr chunk size\n",(unsigned int)physmem);
  chunk = 0;
  while(physmem[chunk].size)
  {
    if (verbose)
      printf("  %8.8x   %8.8x\n",physmem[chunk].addr,physmem[chunk].size);
    chunk++;
  }
  max_chunk = chunk - 1;

  /* walk back over physical memory chunks, looking for a big enough chunk,
     whose pages are not unavailable (avoid chunk 0, the screen) */
  pagenum       = Npages;
  foundpage     = 0;
  foundphysaddr = 0;
  for (chunk=max_chunk; (chunk > 0) && (foundpage == 0); chunk--)
  {
    pages = physmem[chunk].size >> Log2PageSize;
    if ((pages >= pages_needed) &&
        ((physmem[chunk].addr & 0xfff00000) == physmem[chunk].addr) &&
        ((physmem[chunk].size & 0xfff00000) == physmem[chunk].size)    )
    {
      /* chunk big enough, and 1 Mb aligned */
      /* try for end of chunk */
      foundpage     = pagenum-pages_needed;
      foundphysaddr = physmem[chunk].addr + physmem[chunk].size - (pages_needed<<Log2PageSize);
      for (p=foundpage; p<foundpage+pages_needed; p++)
      {
        if (CAM[p].flags & kernelvals.PageFlags_Unavailable)
        {
          foundpage = foundphysaddr = 0;
          break;
        }
      }
      if ((foundpage == 0) && (pages > pages_needed))
      {
        /* try for beginning of chunk */
        foundpage = pagenum-pages;
        foundphysaddr = physmem[chunk].addr;
        for (p=foundpage; p<foundpage+pages_needed; p++)
        {
          if (CAM[p].flags & kernelvals.PageFlags_Unavailable)
          {
            foundpage = foundphysaddr = 0;
            break;
          }
        }
      }
    }
    pagenum -= pages;
  }

  if (verbose)
    printf("foundpage=%1d foundphysaddr=%8.8x\n",foundpage,foundphysaddr);

  *chunk_page     = foundpage;
  *chunk_physaddr = foundphysaddr;
}

/* ------------------------------------------------------------------------ */

static void removeDA(void)
{
  _swix(OS_DynamicArea,_INR(0,1),1,softROM.DAN);
}

/* ------------------------------------------------------------------------ */

static void get_softROM_DA(void)
{
  int             RO370_bug;
  uint32          freesize;
  _kernel_oserror *e;

  static int DAh_word;

  /* RISC OS 3.70 or 3.71 have interrupt hole in page reclaiming kernel
     code when running on StrongARM, which may make DA grow go pop. The
     workaround is to turn off the data cache during the grow, which is
     supported by the secret '*cache id' syntax.
  */
  if ((ROMversion == 370) || (ROMversion == 371))
    RO370_bug = ((svcarmid() & 0xf000) == 0xa000); /* if StrongARM */
  else
    RO370_bug = 0;

  _swix(OS_DynamicArea,_INR(0,1)|_OUT(2),2,DA_FreePool,&freesize);

  if (verbose) printf("Npages=%1d FreeSize=%1dk\n",Npages,freesize>>10);

  if ((Npages<<Log2PageSize) < AbsMinMemSize)
    error_noslot("not enough RAM fitted");

  /* ensure a little left in Free Pool after DA created */
  if (freesize < ROMsize + 64*1024)
    error_noslot("not enough free memory");

  find_contiguous_chunk(Npages,ROMsize >> Log2PageSize,
                        &softROM.page, &softROM.physaddr);
  if (softROM.page == 0)
    error_noslot("no suitable physical RAM chunk found");

  /* create DA to get pages we want - OS does all the hard work of arranging
     pages in order, and reclaiming them from other use if necessary */
  if (RO370_bug) _swix(OS_CLI,_IN(0),"cache iw");
  DAh_word = softROM.page;
  e = _swix(OS_DynamicArea,_INR(0,8)|_OUT(1)|_OUT(3),
            0,-1,ROMsize,-1,0x180,ROMsize,DAhandler,&DAh_word,"softROM",
            &softROM.DAN,&softROM.DAbase);
  if (RO370_bug) _swix(OS_CLI,_IN(0),"cache idw");
  if (e) error_noslot("failed to create temporary DA");
  atexit(removeDA);
}

/* ------------------------------------------------------------------------ */

static void copy_ROM_and_go(void)
{
  memcpy((void *)softROM.DAbase,(void *)ROMlogaddr,ROMsize);

  /* should never return */
  startnew(softROM.physaddr,kernelvals.L1PT,0,kernelvals.ARMA_Cleaner_flipflop);
}

/* ------------------------------------------------------------------------ */

void change_memory(uint32 *ptr, uint32 val)
{
   *ptr = val;
/*   char command[50];

   sprintf(command,"MemoryA &%8.8X &%8.8X",(uint32)ptr,val);*/
}

uint32 *find_sequence(uint32 *start, uint32 *end, uint32 one, uint32 two)
{
   uint32 *res = 0;

   while (res == 0 && start < end)
   {
      if (start[0] == one && start[1] == two)
         res = start;
      start++;
   }

   return res;
}

void make_bl(uint32 *mem, uint32 offset)
{
   int o = 0x3FFEF8 - offset;
   change_memory(mem,0xEB000000 | ((uint32)(o - 8))>>2);
}

void patch_softload(uint32 base)
{
   uint32 *mem = (uint32 *)base;
   uint32 *end = mem + 1024*1024;
   uint32 *t = find_sequence(mem,end,0x131E020F,0x108EE003);

   if (!t)
      return;

   if (find_sequence(t+4,end,0x131E020F,0x108EE003))
      error_noslot("Patch point 1 is not unique.");

   change_memory(t,0xE1A00000);
   change_memory(t+1,0xE1A00000);

   /***************************************/

   t = find_sequence(mem,end,0x19180480,0x18880480);

   if (!t)
      error_noslot("Failed to find patch point 2.");

   if (find_sequence(t+4,end,0x19180480,0x18880480))
      error_noslot("Patch point 2 is not unique.");

   t+=6;

   make_bl(t,(t-mem) * 4);

   change_memory(mem+0xFFFBE,0xE24F4CFF);
   change_memory(mem+0xFFFBF,0xE244483F);
   change_memory(mem+0xFFFC0,0xE1A0F00E);
}

/* ------------------------------------------------------------------------ */

static void load_ROM_and_go(char ROMfname[])
{
  char cmd[256];

  sprintf(cmd,"load %s %x",ROMfname,softROM.DAbase);
  _swix(OS_CLI,_IN(0),cmd);

  patch_softload(softROM.DAbase);

  /* should never return */
  startnew(softROM.physaddr,kernelvals.L1PT,0,kernelvals.ARMA_Cleaner_flipflop);
}

/* ------------------------------------------------------------------------ */

static void unload_ROM_and_go(uint32 ROMpa /* ROM physical address*/)
{
  /* should never return */
  startnew(0,kernelvals.L1PT,ROMpa,kernelvals.ARMA_Cleaner_flipflop);
}

/* ------------------------------------------------------------------------ */

static void wait_cs(uint32 cs)
{
  /* no need to worry about wrap - will be soon after reset */

  uint32 t0,t1;

  _swix(OS_ReadMonotonicTime,_OUT(0),&t0);
  do
  {
    _swix(OS_ReadMonotonicTime,_OUT(0),&t1);
  } while (t1 < t0 + cs);
}

/* ------------------------------------------------------------------------ */

int main(int argc, char *argv[])
{
  int    argf0,argf, memsize, wearefine;
  uint32 ROMpa;
  char   ROMfname[256];

  char   loadtype; /* 'n'=do nothing,'f'=load file,'r'=copy ROM,'u'=unload */

  find_ROMversion();
  if (ROMversion < 360)
  {
    fprintf(stderr,"softload only runs on RISC OS 3.6 or later\n");
    exit(ErrorReturnCode);
  }

  find_kernelvals(ROMversion,&kernelvals);

  /* no. of RAM pages in machine */
  _swix(OS_ReadMemMapInfo,_OUT(1),&Npages);

  argf0 = read_arg_options(argc,argv,&verbose,&min_memsize);
  if (argf0 >= argc)
  {
    fprintf(stderr,"usage: sofload [-v] [-m <minmem>[K|M]] <ROMname> [<ROMname> ...]\n");
    exit(ErrorReturnCode);
  }

  if (verbose) printf("Softload version %s\n",Version);

  ROMpa = ROM_physaddr();

  memsize = Npages << Log2PageSize;
  if (ROMpa != ROMhardaddr) memsize += ROMsize;
  if ((min_memsize != 0) && (memsize < min_memsize))
  {
    /* do nothing if memory size below -m specified option */
    if (verbose) printf("  fitted memory below -m %8.8x\n",min_memsize);
    exit(0);
  }

  wearefine = -1;
  for (argf=argf0; (argf < argc) && (wearefine == -1); argf++)
  {
    read_ROMfname(argf,argv,ROMfname);

    if ((strcmp(ROMfname,".NONE.") == 0) ||
        (strcmp(ROMfname,".none.") == 0)    )
    {
      loadtype = 'u';
    }
    else if ((strcmp(ROMfname,".ROM.") == 0) ||
             (strcmp(ROMfname,".rom.") == 0)    )
    {
      loadtype = 'r';
    }
    else /* assume load from file */
    {
      loadtype = 'f';
    }

    wearefine = arewefine(loadtype,ROMpa,ROMfname);
  }

  if (wearefine == -1) error_noslot("file(s) not found or bad");

  if (wearefine) loadtype = 'n';

  if ((loadtype == 'r') || (loadtype == 'f'))
  {
    if (ROMpa != ROMhardaddr)
    {
      /* - tackle soft load over different soft load by unloading first
         - next boot will do the proper load
         - this avoids need for enough memory for two soft ROMs */
      strcpy(ROMfname,".none.");
      loadtype = 'u';
    }
  }

  switch(loadtype)
  {
    case 'u':
      printf("soft-unloading ROM...\n");
      wait_cs(75);
      unload_ROM_and_go(ROMpa);
      break;
    case 'r':
      printf("soft-copying ROM...\n");
      get_softROM_DA();
      copy_ROM_and_go();
      break;
    case 'f':
      printf("soft-loading ROM...\n");
      get_softROM_DA();
      load_ROM_and_go(ROMfname);
      break;
    case 'n':
      /* do nothing */
      if (ROMpa != ROMhardaddr) printf("soft-loaded OS\n");
      break;
  }

  return 0;
}
